/**
 * A Paving stone.
 * @file pavingstone.cpp
 * @see http://code.google.com/p/mega8-hob/source/browse/trunk/pavingstone.cpp
 * @author Hamish Cumming & Brian Thorne
 * @version see SVN revision number
 */

#include "pavingstone.h"
#include "paver.h"
#include "polygon.h"
#include "atto.h"
#include "linked_list.h"
#include <math.h>


#define MAX_BRICKS 10000
static point_t stone_table[MAX_BRICKS];
/* A count of paving stones in the polygon */
static int number_stones = 0;
/* A count of paving stones that intersect the polygon */
static int number_stones_cut = 0;

/**
 * Scale a pattern by a float.
 * @param pattern To Scale
 * @param scale The number to scale by.
 * @return scaled Pattern
 */
pavingpattern_t scale_pattern(pavingpattern_t pattern, float scale)
{
    pattern.stone.psx = (int)(pattern.stone.psx * scale);
    pattern.stone.psy = (int)(pattern.stone.psy * scale);
    pattern.unitvector1.x = (int)(pattern.unitvector1.x * scale);
    pattern.unitvector1.y = (int)(pattern.unitvector1.y * scale);
    pattern.unitvector2.x = (int)(pattern.unitvector2.x * scale);
    pattern.unitvector2.y = (int)(pattern.unitvector2.y * scale);
    pattern.stone2offset.x = (int)(pattern.stone2offset.x * scale);
    pattern.stone2offset.y = (int)(pattern.stone2offset.y * scale);
    return pattern;
}

/**
 * Lays out the pavingstones in the polygon along the given segment.
 * Assumes the segment is part of the polygon, and that the polygon is drawn clockwise.
 * @param polygon The polygon to lay the stones in
 * @param segment The edge of the polygon to lay the stones against
 * @param pattern The paving pattern to use
 * @param *stones_cut A pointer to return the number of stones that intersected the polygon
 */
int lay_pavingstones(linked_list_t * polygon, segment_t segment, pavingpattern_t pattern, int * stones_cut)
{
    //Work out the angle between unitvector1 and segment
    line_t line = line_from_seg(segment);
    float theta1, theta2;

    if (line.b == 0)
        theta1 = PI/2;
    else 
        theta1 = atan(line.a / line.b);
    if (pattern.unitvector1.x == 0)
    {
        if (pattern.unitvector1.y > 0)
            theta2 = PI/2;
        else
            theta2 = -PI/2;
    }
    else
        theta2 = atan((float)pattern.unitvector1.y / (float)pattern.unitvector1.x) + PI;

    if (segment.start.y < segment.end.y && theta1 > 0) //atan returns the smallest value
        theta1 -= PI;
    if (segment.start.y > segment.end.y && theta1 < 0)
        theta1 += PI;

    float angle = theta1 - theta2; //the difference - how much to rotate all the stones

    point_t pnt = segment.start; //base point for the first stone
    number_stones = number_stones_cut = 0;

    //rotate the unit vectors
    vector_t uv1 = rotate_vector(pattern.unitvector1, angle);
    vector_t uv2 = rotate_vector(pattern.unitvector2, angle);

    /* Recursively Lay the bricks the directions of +- the two unit vectors,
        using a table to keep track of where we have already been */
    lay_pavingstone (polygon, pattern, angle, pnt);
    pnt.x += uv1.x;
    pnt.y -= uv1.y; //-ve as y is defined + is up for stones
    lay_pavingstone(polygon, pattern, angle, pnt);
    pnt.x = segment.start.x - uv1.x; 
    pnt.y = segment.start.y + uv1.y;
    lay_pavingstone(polygon, pattern, angle, pnt);
    pnt.x = segment.start.x + uv2.x;
    pnt.y = segment.start.y - uv2.y;
    lay_pavingstone(polygon, pattern, angle, pnt);
    pnt.x = segment.start.x - uv2.x;
    pnt.y = segment.start.y + uv2.y;
    lay_pavingstone(polygon, pattern, angle, pnt);

    *stones_cut  = number_stones_cut;
    return number_stones;
}

/**
 * Lays out a pavingstone in the polygon at the point and with the angle given.
 * @param polygon The polygon to lay the stone in
 * @param stone The pattern to use.
 * @param angle The angle to use
 * @param point A point to use as the origin for this stone.
 */
void lay_pavingstone(linked_list_t * polygon, pavingpattern_t stone, float angle, point_t point)
{
    if (pavingstone_in_table(point)){
        return;
    }
    if (pavingstone_outside_polygon(polygon, stone, angle, point)){
        if (stone.pavingstones == 2)//draw the second stone
        {
            point_t p = point;
            vector_t v = rotate_vector(stone.stone2offset, angle);
            p.x += v.x;
            p.y -= v.y;//- because defined y as up in stone
            if (!pavingstone_outside_polygon(polygon, stone,angle + stone.stone2angle, p))
            {
                draw_pavingstone(stone, angle + stone.stone2angle, p);
                if (stone_intersects_pgon(polygon, stone, angle + stone.stone2angle, p))
                    number_stones_cut++;
                add_pavingstone_to_table(p);
            }
        }
        return;
    }
    draw_pavingstone(stone, angle, point);
    if (stone_intersects_pgon(polygon, stone, angle, point))
        number_stones_cut++;
    add_pavingstone_to_table(point);
    
    if (stone.pavingstones == 2)//draw the second stone
    {
        point_t p = point;
        vector_t v = rotate_vector(stone.stone2offset, angle);
        p.x += v.x;
        p.y -= v.y;//defined y as up in stone
        if (!pavingstone_outside_polygon(polygon, stone, angle + stone.stone2angle, p)){
            draw_pavingstone(stone, angle + stone.stone2angle, p);
            add_pavingstone_to_table(p);
        }
    }

    //now lay paving stones in the four directions around it
    point_t pnt = point;
    vector_t uv1 = rotate_vector(stone.unitvector1, angle);
    vector_t uv2 = rotate_vector(stone.unitvector2, angle);

    pnt.x += uv1.x;
    pnt.y -= uv1.y;
    lay_pavingstone(polygon, stone, angle, pnt);
    pnt = point;
    pnt.x -= uv1.x;
    pnt.y += uv1.y;
    lay_pavingstone(polygon, stone, angle, pnt);
    pnt = point;
    pnt.x += uv2.x;
    pnt.y -= uv2.y;
    lay_pavingstone(polygon, stone, angle, pnt);
    pnt = point;
    pnt.x -= uv2.x;
    pnt.y += uv2.y;
    lay_pavingstone(polygon, stone, angle, pnt);
}

/**
 * Checks if the stone at point has already been drawn.
 * @param point The location of the stone to check.
 * @return Is there a stone there.
 */
int pavingstone_in_table(point_t point)
{
    if (number_stones >= MAX_BRICKS)
        return 1;
    
    for (int i = 0; i < number_stones; i++)
    {
        if ((abs(stone_table[i].x - point.x) < 2) && (abs(stone_table[i].y - point.y) < 2))
            return 1;
    }
    return 0;
}

void add_pavingstone_to_table(point_t point)
{
    stone_table[number_stones++] = point;
}

/** 
 * Returns true (1) if the rectangle lies within polyogn.
 * If one of the corners of the rectangle lie within the polygon, it is deemed to be inside
 */
int pavingstone_outside_polygon(linked_list_t * polygon, pavingpattern_t pattern, float angle, point_t point)
{
    segment_t seg1, seg2, seg3, seg4;

    seg1.start = point;
    seg1.end.x = seg1.start.x - (int)(pattern.stone.psy*sin(angle));
    seg1.end.y = seg1.start.y - (int)(pattern.stone.psy*cos(angle));

    seg2.start = seg1.end;
    seg2.end.x = seg2.start.x + (int)(pattern.stone.psx*cos(angle));
    seg2.end.y = seg2.start.y - (int)(pattern.stone.psx*sin(angle));

    seg3.start = seg2.end;
    seg3.end.x = seg3.start.x + (int)(pattern.stone.psy*sin(angle));
    seg3.end.y = seg3.start.y + (int)(pattern.stone.psy*cos(angle));

    seg4.start = seg3.end;
    seg4.end = point;

    if (!pnt_outside_pgon(polygon, seg1.start) || !pnt_outside_pgon(polygon, seg2.start) 
    || !pnt_outside_pgon(polygon, seg3.start) || !pnt_outside_pgon(polygon, seg4.start))
        return 0;  //at least one point is in the pgon
    if (stone_intersects_pgon(polygon, pattern, angle, point))
        return 0;
    return 1;
}

/**
 * Draws a paving stone
 * @param pattern The paving pattern to use
 * @param angle The angle the pattern is rotated by
 * @param point The point at which to draw the stone
 */
void draw_pavingstone(pavingpattern_t pattern, float angle, point_t point)
{
    segment_t seg1;
    segment_t seg2;
    segment_t seg3;
    segment_t seg4;
    
    seg1.start = point;
    
    seg1.end.x = seg1.start.x - (int)(pattern.stone.psy*sin(angle));
    seg1.end.y = seg1.start.y - (int)(pattern.stone.psy*cos(angle));
    
    seg2.start = seg1.end;
    
    seg2.end.x = seg2.start.x + (int)(pattern.stone.psx*cos(angle));
    seg2.end.y = seg2.start.y - (int)(pattern.stone.psx*sin(angle));
    
    seg3.start = seg2.end;
    
    seg3.end.x = seg3.start.x + (int)(pattern.stone.psy*sin(angle));
    seg3.end.y = seg3.start.y + (int)(pattern.stone.psy*cos(angle));
    
    seg4.start = seg3.end;
    seg4.end = point;
    
    atto_color(pattern.stone.colour.r, pattern.stone.colour.g, pattern.stone.colour.b);
    draw_segment(seg1);
    draw_segment(seg2);
    draw_segment(seg3);
    draw_segment(seg4);
    
}

/**
 * Draws a segment using the current colour
 */
void draw_segment(segment_t segment)
{
    atto_line(segment.start.x, segment.start.y, segment.end.x, segment.end.y);
}

/**
 * Roates a vector through an angle
 * @param vector The vector to rotate
 * @param angle The angle to rotate the vector by
 * @return The rotated vector
 */
vector_t rotate_vector(vector_t vector, float angle)
{
    float h = sqrt(vector.x * vector.x + vector.y * vector.y);
    float theta;
    if (vector.x == 0){
        if (vector.y > 0)
            theta = PI/2;
        else
            theta = -PI/2;
    }
    else
        theta = atan((float)vector.y / (float)vector.x);

    vector_t retvector;
    retvector.x = (int)(cos(theta + angle) * h);
    retvector.y = (int)(sin(theta + angle) * h);
    return retvector;
}

/**
 * Determines if a stone intersects a polygon
 * @param polygon The polygon to find intersections with
 * @param pattern The pattern containing the stone to find interections with
 * @param angle The angle the stone is rotated by
 * @param point The point of the start vertex of the stone
 * @return True if the stone intersects the polygon, false otherwise.
 */
int stone_intersects_pgon(linked_list_t * polygon, pavingpattern_t pattern, float angle, point_t point)
{
    segment_t seg1;
    segment_t seg2;
    segment_t seg3;
    segment_t seg4;
    segment_t poly_seg;
    
    seg1.start = point;
    
    seg1.end.x = seg1.start.x - (int)(pattern.stone.psy*sin(angle));
    seg1.end.y = seg1.start.y - (int)(pattern.stone.psy*cos(angle));
    
    seg2.start = seg1.end;
    
    seg2.end.x = seg2.start.x + (int)(pattern.stone.psx*cos(angle));
    seg2.end.y = seg2.start.y - (int)(pattern.stone.psx*sin(angle));
    
    seg3.start = seg2.end;
    
    seg3.end.x = seg3.start.x + (int)(pattern.stone.psy*sin(angle));
    seg3.end.y = seg3.start.y + (int)(pattern.stone.psy*cos(angle));
    
    seg4.start = seg3.end;
    seg4.end = point;

    list_node_t * node = polygon->start_node;
    while (node->next != NULL)
    {
        poly_seg.start = *node->point;
        poly_seg.end = *node->next->point;
        if (segments_intersect(seg1, poly_seg))
            return 1;
        if (segments_intersect(seg2, poly_seg))
            return 1;
        if (segments_intersect(seg3, poly_seg))
            return 1;
        if (segments_intersect(seg4, poly_seg))
            return 1;
        node = node-> next;
    }
    poly_seg.start = *polygon->end_node->point;
    poly_seg.start = *polygon->start_node->point;
    if (segments_intersect(seg1, poly_seg))
        return 1;
    if (segments_intersect(seg2, poly_seg))
        return 1;
    if (segments_intersect(seg3, poly_seg))
         return 1;
    if (segments_intersect(seg4, poly_seg))
        return 1;
    return 0;
}
